#include "stdafx.h"
#include "Mesh.h"

#include "ContentManager.h"

namespace Framework {

Mesh::Mesh(Game* game) : DrawableGameComponent(game) {
    this->SetPosition(D3DXVECTOR3(0, 0, 0));
    this->SetRotation(D3DXVECTOR3(0, 0, 0));
    this->SetScale(D3DXVECTOR3(1, 1, 1));

    this->cullMode = D3DCULL_CCW;
    this->effect = NULL;
    this->effectFile = NULL;
    this->worldMatrixNeedsUpdate = true;
    this->mesh = NULL;
    this->meshFile = NULL;
    this->boundingBox = new d3d::BoundingBox();
    this->boundingBoxNeedsUpdate = true;
    this->boundingSphere = new d3d::BoundingSphere();
    this->boundingSphereNeedsUpdate = true;
    D3DXMatrixIdentity(&this->rotationMatrix);
}

Mesh::~Mesh(void) {
    d3d::Delete(this->boundingBox);
    d3d::Delete(this->boundingSphere);
}

d3d::BoundingBox* Mesh::GetBoundingBox() {
    if (this->mesh == NULL) {
        return NULL;
    }

    if (!this->boundingBoxNeedsUpdate && !this->worldMatrixNeedsUpdate) {
        return this->boundingBox;
    }

    D3DXMATRIX world = this->GetWorldMatrix();

    d3d::BoundingBox* box = this->mesh->GetBoundingBox();
    D3DXVECTOR3 b1;
    D3DXVECTOR3 b2;
    D3DXVec3TransformCoord(&b1, &box->_max, &world);
    D3DXVec3TransformCoord(&b2, &box->_min, &world);

    if (b1.x > b2.x) {
        this->boundingBox->_max.x = b1.x;
        this->boundingBox->_min.x = b2.x;
    } else {
        this->boundingBox->_max.x = b2.x;
        this->boundingBox->_min.x = b1.x;
    }

    if (b1.y > b2.y) {
        this->boundingBox->_max.y = b1.y;
        this->boundingBox->_min.y = b2.y;
    } else {
        this->boundingBox->_max.y = b2.y;
        this->boundingBox->_min.y = b1.y;
    }

    if (b1.z > b2.z) {
        this->boundingBox->_max.z = b1.z;
        this->boundingBox->_min.z = b2.z;
    } else {
        this->boundingBox->_max.z = b2.z;
        this->boundingBox->_min.z = b1.z;
    }

    this->boundingBoxNeedsUpdate = false;
    return this->boundingBox;
}

d3d::BoundingSphere* Mesh::GetBoundingSphere() {
    if (this->mesh == NULL) {
        return NULL;
    }

    if (!this->boundingBoxNeedsUpdate && !this->worldMatrixNeedsUpdate) {
        return this->boundingSphere;
    }

    d3d::BoundingSphere* sphere = this->mesh->GetBoundingSphere();
    this->boundingSphere->_center = sphere->_center + this->position;
    FLOAT len = D3DXVec3Length(&this->GetScale());
    FLOAT one = D3DXVec3Length(&D3DXVECTOR3(1, 1, 1));
    this->boundingSphere->_radius = sphere->_radius * (len / one);

    this->boundingSphereNeedsUpdate = false;
    return this->boundingSphere;
}

D3DCULL Mesh::GetCullMode() const {
    return this->cullMode;
}

void Mesh::SetCullMode(D3DCULL val) {
    this->cullMode = val;
}

D3DXMATRIX Mesh::GetWorldMatrix() {
    if (!worldMatrixNeedsUpdate) {
        return this->worldMatrix;
    }

    D3DXQUATERNION quaternion;
    D3DXQuaternionRotationMatrix(&quaternion, &this->rotationMatrix);
    D3DXMatrixTransformation(&this->worldMatrix, NULL, NULL, &this->scale, NULL, &quaternion, &this->position);
    this->worldMatrixNeedsUpdate = false;
    return this->worldMatrix;
}

D3DXMesh* Mesh::GetMesh() const {
    return this->mesh;
}

void Mesh::SetMesh(D3DXMesh* val) {
    this->mesh = val;
}

D3DXVECTOR3 Mesh::GetPosition() const {
    return this->position;
}

void Mesh::SetPosition(D3DXVECTOR3 val) {
    this->position = val;
    this->worldMatrixNeedsUpdate = true;
    this->boundingBoxNeedsUpdate = true;
}

D3DXVECTOR3 Mesh::GetRotation() const {
    return this->rotation;
}

void Mesh::SetRotation(D3DXVECTOR3 val) {
    this->rotation = val;
    D3DXMatrixRotationYawPitchRoll(&this->rotationMatrix, this->rotation.y, this->rotation.x, this->rotation.z);
    this->worldMatrixNeedsUpdate = true;
    this->boundingBoxNeedsUpdate = true;
}

D3DXVECTOR3 Mesh::GetScale() const {
    return this->scale;
}

void Mesh::SetScale(D3DXVECTOR3 val) {
    this->scale = val;
    this->worldMatrixNeedsUpdate = true;
    this->boundingBoxNeedsUpdate = true;
}

void Mesh::SetEffectFile(LPCSTR effectFile) {
    this->effectFile = effectFile;
}

void Mesh::SetMeshFile(LPCSTR meshFile) {
    this->meshFile = meshFile;
}

void Mesh::Draw(FLOAT timeDelta) {
    this->Draw2(&this->GetWorldMatrix());
}

void Mesh::Draw2(D3DXMATRIX* matrix) {
    if (this->mesh == NULL) {
        return;
    }

    this->GetGame()->GetGraphicsDevice()->SetTransform(D3DTS_WORLD, matrix);
    if (this->cullMode != D3DCULL_NONE && this->GetGame()->GetCamera()->GetMirror()) {
        if (this->cullMode == D3DCULL_CCW) {
            this->GetGame()->GetGraphicsDevice()->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
        } else if (this->cullMode == D3DCULL_CW) {
            this->GetGame()->GetGraphicsDevice()->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
        }
    } else {
        this->GetGame()->GetGraphicsDevice()->SetRenderState(D3DRS_CULLMODE, this->cullMode);
    }

    if (this->effectFile != NULL) {
        UINT iPass, cPasses;
        this->effect->Begin(&cPasses, 0);

        for (iPass = 0; iPass < cPasses; iPass++) {
            this->effect->BeginPass(iPass) ;
            for (UINT i = 0; i < this->mesh->GetMaterials()->size(); i++) {
                this->GetGame()->GetGraphicsDevice()->SetMaterial(&(*this->mesh->GetMaterials())[i]);
                this->GetGame()->GetGraphicsDevice()->SetTexture(0, (*this->mesh->GetTextures())[i]);
                this->DrawSubset(i);
            }

            this->effect->EndPass() ;
        }

        this->effect->End() ;
    } else {
        for (UINT i = 0; i < this->mesh->GetMaterials()->size(); i++) {
            this->GetGame()->GetGraphicsDevice()->SetMaterial(&(*this->mesh->GetMaterials())[i]);
            this->GetGame()->GetGraphicsDevice()->SetTexture(0, (*this->mesh->GetTextures())[i]);
            this->mesh->GetMesh()->DrawSubset(i);
        }
    }
}

void Mesh::DrawSubset(DWORD attribId) {
    this->mesh->GetMesh()->DrawSubset(attribId);
}

void Mesh::Initialize() {
}

void Mesh::LoadContent() {
    this->LoadEffect();
    this->LoadMesh();
}

void Mesh::LoadEffect() {
    if (this->effectFile == NULL) {
        return;
    }

    this->effect = this->GetGame()->GetContentManager()->LoadEffect(this->effectFile);
}

void Mesh::LoadMesh() {
    if (this->meshFile == NULL) {
        return;
    }

    HRESULT hr = 0;

    this->mesh = this->GetGame()->GetContentManager()->LoadMesh(this->meshFile);
}

void Mesh::UnloadContent() {
}

void Mesh::Update(FLOAT timeDelta) {
}

}
